package org.vison.wonfu.framework.soa.protocol;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.hc.client5.http.async.methods.SimpleBody;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.message.BasicNameValuePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vison.wonfu.framework.commons.utils.ArrayUtils;
import org.vison.wonfu.framework.commons.utils.CollectionUtils;
import org.vison.wonfu.framework.commons.utils.StringUtils;
import org.vison.wonfu.framework.commons.utils.TypeUtils;
import org.vison.wonfu.framework.soa.Invocation;
import org.vison.wonfu.framework.soa.annotation.SOAResultMode;
import org.vison.wonfu.framework.soa.context.SoaApplicationContext;
import org.vison.wonfu.framework.soa.exception.RpcException;

import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;

import static java.util.Optional.ofNullable;
import static org.vison.wonfu.framework.soa.protocol.jsonRpc.JsonRpcConstants.*;
import static org.vison.wonfu.framework.soa.exception.RpcException.*;
public abstract class AbstractParamsHandler implements ParamsHandler {

    private final static Logger log = LoggerFactory.getLogger(AbstractParamsHandler.class);

    @Override
    public SimpleHttpRequest pack(Invocation invocation, ObjectMapper serialization) throws Exception {

        Object[] arguments = invocation.getArguments();
        Map<String,Object> requestParams = new HashMap<>();
        if (arguments != null && arguments.length > 0) {
           requestParams = doResolverArgument(requestParams, arguments[0], getContextObjectMapper(invocation));
        }
        String requestPath = buildRequestPath(invocation.getServicePath(), invocation.getMethodPath(), requestParams);
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(invocation.getConnectionTimeout(), TimeUnit.MILLISECONDS)
                .setConnectionRequestTimeout(invocation.getConnectionTimeout(), TimeUnit.MILLISECONDS)
                .setResponseTimeout(invocation.getTimeout(), TimeUnit.MILLISECONDS)
                .setCircularRedirectsAllowed(true)
                .setRedirectsEnabled(true)
                .build();

        String uri = new HttpHost(getScheme(invocation.isHttps()), invocation.getHost(),
                invocation.getPort() <= 0 ? -1 : invocation.getPort()).toURI() + requestPath;
        SimpleHttpRequest request = doPack(invocation, uri, arguments);
        // 处理额外的header 即业务代码设置的相关的header
        ofNullable(invocation.getRawAttachments()).ifPresent(extraHeader -> extraHeader.forEach(request::addHeader));
        request.setConfig(requestConfig);

        return request;
    }

    protected byte[] writeValueAsBytes(ObjectMapper objectMapper, Object value) {
        try {
            return objectMapper.writeValueAsBytes(value);
        } catch (JsonProcessingException e) {
            throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, "json rpc pack error", e);
        }
    }

    @Override
    public Object unpack(Object response,Invocation invocation, URL url) throws Exception {
        ObjectMapper serialization = getContextObjectMapper(invocation);
        SimpleHttpResponse httpResponse = (SimpleHttpResponse) response;
        String responseBody = null;
        SimpleBody simpleBody = httpResponse.getBody();
        if (simpleBody != null && simpleBody.getBodyBytes() != null){
            responseBody = new String(simpleBody.getBodyBytes(), StandardCharsets.UTF_8);
            final String mdcResponseBody = responseBody;
            invocation.putDMCLog("RESPONSE_BODY", () -> mdcResponseBody, Level.INFO);
        }
        int responseCode = httpResponse.getCode();
        boolean isSuccess = isSuccess(responseCode);
        if (!isSuccess) {
            throw new RpcException(NETWORK_EXCEPTION, "responseCode:" + responseCode);
        }

        if (StringUtils.isNotEmpty(responseBody)) {
//            JsonNode bodyNode  = resolveBody(responseBody, invocation.getReturnType(), serialization);
            return doUnpack(responseBody, invocation, serialization);
        }
        return null;
    }

    public Object doUnpack(String needUnpackBody, Invocation invocation, ObjectMapper serialization) throws Exception {
         // 解析返回值
        boolean completeResult = invocation.isCompleteResult();
        SOAResultMode resultMode = invocation.getResultMode();

        Class<?> returnType = invocation.getReturnType();
        Type returnGenericType = invocation.getReturnTypes()[1];
        if (returnType.equals(Void.TYPE)) {
            return null;
        }
        return resolveBody(needUnpackBody,returnGenericType , serialization);
    }

    private String getScheme(boolean https) {
        return https  ? "https" : "http";
    }


    protected Object resolverArgument(Object[] arguments, List<String> paramNames, Map<String, Object> requestParamsMap, ObjectMapper serialization, URL url) {
        Map<String, Object> argumentMergeMap = new HashMap<>(requestParamsMap);
        if (CollectionUtils.isNotEmpty(paramNames)) {
            if (paramNames.size() != arguments.length) {
                throw new IllegalArgumentException("Param annotations must be added to all parameters, arguments:" + Arrays.toString(arguments) + ",paramSignatures:" + paramNames);
            }
            for (int i = 0; i < paramNames.size(); i++) {
                Object argument = arguments[i];
                if (argument != null) {
                    argumentMergeMap.put(paramNames.get(i), argument);
                }
            }
            return argumentMergeMap;
        }
        return doResolverArgument(arguments, argumentMergeMap, serialization, url);
    }


    protected Object doResolverArgument(Object[] arguments, Map<String, Object> requestParams, ObjectMapper serialization, URL url) {
        if (ArrayUtils.isEmpty(arguments)) {
            return requestParams;
        }
        if (arguments.length > 1) {
            throw new IllegalArgumentException("The Method argument params must only one argument[" + Arrays.toString(arguments) + "]");
        }
        Object argument = arguments[0];
        if (TypeUtils.isPrimitive(argument.getClass())) {
            throw new IllegalArgumentException("The Method argument params must be Map or Object argument[" + Arrays.toString(arguments) + "]");
        }
        return doResolverArgument(requestParams, argument, serialization);
    }



    protected Map<String, Object> doResolverArgument(Map<String, Object> requestParams, Object argument,ObjectMapper serialization) {
        if (argument instanceof Map) {
            requestParams.putAll(CollectionUtils.removeNullValue((Map<String, Object>) argument));
        } else {
            if (argument.getClass().equals(Object.class)) {
                return requestParams;
            }
            Map<String, Object> params  =  serialization.convertValue(argument, new TypeReference<Map<String, Object>>() {});
            requestParams.putAll(params);
        }
        return requestParams;
    }


    public Object resolveBody(String responseBody,Type returnGenericType, ObjectMapper objectMapper) throws IOException {
        JavaType javaType = objectMapper.constructType(returnGenericType);
        return objectMapper.readValue(responseBody, javaType);
    }

    /**
     * 构建请求路径
     * @param servicePath 服务路径
     * @param methodPath 方法路径
     * @param requestParams 请求参数
     * @return 请求路径
     */
    protected String buildRequestPath(String servicePath, String methodPath, Map<String,Object> requestParams) {
        String requestPath;
        if (servicePath == null) {
            servicePath = "";
        }
        if (servicePath.startsWith(PATH_PREFIX)) {
            requestPath = servicePath;
        } else {
            requestPath = PATH_PREFIX + servicePath;
        }
        if (StringUtils.isNotEmpty(methodPath)) {
            if (requestPath.endsWith(PATH_PREFIX)) {
                requestPath = StringUtils.removeEnd(requestPath, PATH_PREFIX) + methodPath;
            } else {
                requestPath = requestPath + methodPath;
            }
        }
        requestPath = StringUtils.pathFormat(requestPath, requestParams);
        return requestPath;
    }


   protected List<NameValuePair> buildNameValuePair(Invocation invocation,Object argument, ObjectMapper objectMapper) {
       List<NameValuePair> parameters = new ArrayList<>();
       String methodPath = invocation.getMethodPath();
       // 提取methodPath中的占位符
       Set<String> pathParams = StringUtils.extractPathVariables(methodPath);
       if (argument == null) {
           return parameters;
       }
       // 处理Map类型参数  当argument中有在methodPath中的占位符同名的key 过滤掉
       if (argument instanceof Map) {
           Map<String, Object> paramMap = (Map<String, Object>) argument;
           for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
               if (entry.getValue() != null && !pathParams.contains(entry.getKey())) {
                   parameters.add(new BasicNameValuePair(entry.getKey(), entry.getValue().toString()));
               }
           }
       } else {
           // 处理普通Java对象，将其转换为Map
           Map<String, Object> paramMap = objectMapper.convertValue(argument, new TypeReference<Map<String, Object>>() {});
           for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
               if (entry.getValue() != null && !pathParams.contains(entry.getKey())) {
                   parameters.add(new BasicNameValuePair(entry.getKey(), entry.getValue().toString()));
               }
           }
       }
       
       return parameters;
   }

    private boolean isSuccess(int code) {
        return code >= HttpStatus.SC_SUCCESS & code <= HttpStatus.SC_IM_USED;
    }

    protected ObjectMapper getContextObjectMapper(Invocation invocation) {
        Object mapper = SoaApplicationContext.getCustomizeObjectMapper(invocation.getRemoteApplication());
        if(mapper instanceof ObjectMapper){
            return (ObjectMapper)mapper;
        }
        throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, "The application[" + invocation.getRemoteApplication() + "] not found ObjectMapper");
    }

    protected abstract SimpleHttpRequest doPack(Invocation invocation, String uri, Object[] argument) throws RpcException;
}